/**
 * @(#)Items.java	1.22 03/01/23
 *
 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.sun.tools.javac.v8.comp;

import com.sun.tools.javac.v8.code.ByteCodes;
import com.sun.tools.javac.v8.code.CRTable;
import com.sun.tools.javac.v8.code.Code;
import com.sun.tools.javac.v8.code.Flags;
import com.sun.tools.javac.v8.code.Pool;
import com.sun.tools.javac.v8.code.Symbol;
import com.sun.tools.javac.v8.code.Symtab;
import com.sun.tools.javac.v8.code.Type;
import com.sun.tools.javac.v8.code.TypeTags;
import com.sun.tools.javac.v8.code.Code.Chain;
import com.sun.tools.javac.v8.code.Symbol.VarSymbol;
import com.sun.tools.javac.v8.code.Type.MethodType;
import com.sun.tools.javac.v8.tree.Tree;

/**
 * A helper class for code generation. Items are objects that stand for
 * addressable entities in the bytecode. Each item supports a fixed protocol for
 * loading the item on the stack, storing into it, converting it into a jump
 * condition, and several others. There are many individual forms of items, such
 * as local, static, indexed, or instance variables, values on the top of stack,
 * the special values this or super, etc. Individual items are represented as
 * inner classes in class Items.
 */
public class Items implements ByteCodes, TypeTags {

	/**
	 * The current constant pool.
	 */
	Pool pool;

	/**
	 * The current code buffer.
	 */
	Code code;

	/**
	 * The current symbol table.
	 */
	Symtab syms;

	/**
	 * Items that exist only once (flyweight pattern).
	 */
	private final Item voidItem;
	private final Item thisItem;
	private final Item superItem;
	private final Item[] stackItem = new Item[TypeCodeCount];

	public Items(Pool pool, Code code, Symtab syms) {
		super();
		this.code = code;
		this.pool = pool;
		voidItem = new Item(VOIDcode) {

			public String toString() {
				return "void";
			}
		};
		thisItem = new SelfItem(false);
		superItem = new SelfItem(true);
		for (int i = 0; i < VOIDcode; i++)
			stackItem[i] = new StackItem(i);
		stackItem[VOIDcode] = voidItem;
		this.syms = syms;
	}

	/**
	 * Make a void item
	 */
	Item makeVoidItem() {
		return voidItem;
	}

	/**
	 * Make an item representing `this'.
	 */
	Item makeThisItem() {
		return thisItem;
	}

	/**
	 * Make an item representing `super'.
	 */
	Item makeSuperItem() {
		return superItem;
	}

	/**
	 * Make an item representing a value on stack.
	 * 
	 * @param type
	 *            The value's type.
	 */
	Item makeStackItem(Type type) {
		return stackItem[Code.typecode(type)];
	}

	/**
	 * Make an item representing an indexed expression.
	 * 
	 * @param type
	 *            The expression's type.
	 */
	Item makeIndexedItem(Type type) {
		return new IndexedItem(type);
	}

	/**
	 * Make an item representing a local variable.
	 * 
	 * @param v
	 *            The represented variable.
	 */
	Item makeLocalItem(VarSymbol v) {
		return new LocalItem(v.erasure(), v.adr);
	}

	/**
	 * Make an item representing a local anonymous variable.
	 * 
	 * @param type
	 *            The represented variable's type.
	 * @param reg
	 *            The represented variable's register.
	 */
	Item makeLocalItem(Type type, int reg) {
		return new LocalItem(type, reg);
	}

	/**
	 * Make an item representing a static variable or method.
	 * 
	 * @param member
	 *            The represented symbol.
	 */
	Item makeStaticItem(Symbol member) {
		return new StaticItem(member);
	}

	/**
	 * Make an item representing an instance variable or method.
	 * 
	 * @param member
	 *            The represented symbol.
	 * @param nonvirtual
	 *            Is the reference not virtual? (true for constructors and
	 *            private members).
	 */
	Item makeMemberItem(Symbol member, boolean nonvirtual) {
		return new MemberItem(member, nonvirtual);
	}

	/**
	 * Make an item representing a literal.
	 * 
	 * @param t
	 *            The literal's type.
	 * @param value
	 *            The literal's value.
	 */
	Item makeImmediateItem(Type type, Object value) {
		return new ImmediateItem(type, value);
	}

	/**
	 * Make an item representing an assignment expression.
	 * 
	 * @param lhs
	 *            The item representing the assignment's left hand side.
	 */
	Item makeAssignItem(Item lhs) {
		return new AssignItem(lhs);
	}

	/**
	 * Make an item representing a conditional or unconditional jump.
	 * 
	 * @param opcode
	 *            The jump's opcode.
	 * @param trueJumps
	 *            A chain encomassing all jumps that can be taken if the
	 *            condition evaluates to true.
	 * @param falseJumps
	 *            A chain encomassing all jumps that can be taken if the
	 *            condition evaluates to false.
	 */
	CondItem makeCondItem(int opcode, Chain truejumps, Chain falsejumps) {
		return new CondItem(opcode, truejumps, falsejumps);
	}

	/**
	 * Make an item representing a conditional or unconditional jump.
	 * 
	 * @param opcode
	 *            The jump's opcode.
	 */
	CondItem makeCondItem(int opcode) {
		return makeCondItem(opcode, null, null);
	}

	/**
	 * The base class of all items, which implements default behavior.
	 */
	abstract class Item {

		/**
		 * The type code of values represented by this item.
		 */
		int typecode;

		Item(int typecode) {
			super();
			this.typecode = typecode;
		}

		/**
		 * Generate code to load this item onto stack.
		 */
		Item load() {
			throw new AssertionError();
		}

		/**
		 * Generate code to store top of stack into this item.
		 */
		Item store() {
			throw new AssertionError("store unsupported: " + this);
		}

		/**
		 * Generate code to invoke method represented by this item.
		 */
		Item invoke() {
			throw new AssertionError(this.toString());
		}

		/**
		 * Generate code to use this item twice.
		 */
		void duplicate() {
		}

		/**
		 * Generate code to avoid having to use this item.
		 */
		void drop() {
		}

		/**
		 * Generate code to stash a copy of top of stack - of typecode toscode -
		 * under this item.
		 */
		void stash(int toscode) {
			stackItem[toscode].duplicate();
		}

		/**
		 * Generate code to turn item into a testable condition.
		 */
		CondItem mkCond() {
			load();
			return makeCondItem(ifne);
		}

		/**
		 * Generate code to coerce item to given type code.
		 * 
		 * @param targetcode
		 *            The type code to coerce to.
		 */
		Item coerce(int targetcode) {
			if (typecode == targetcode)
				return this;
			else {
				load();
				int typecode1 = Code.truncate(typecode);
				int targetcode1 = Code.truncate(targetcode);
				if (typecode1 != targetcode1) {
					int offset = targetcode1 > typecode1 ? targetcode1 - 1
							: targetcode1;
					code.emitop(i2l + typecode1 * 3 + offset);
				}
				if (targetcode != targetcode1) {
					code.emitop(int2byte + targetcode - BYTEcode);
				}
				return stackItem[targetcode];
			}
		}

		/**
		 * Generate code to coerce item to given type.
		 * 
		 * @param targettype
		 *            The type to coerce to.
		 */
		Item coerce(Type targettype) {
			return coerce(Code.typecode(targettype));
		}

		/**
		 * Return the width of this item on stack as a number of words.
		 */
		int width() {
			return 0;
		}

		public abstract String toString();
	}

	/**
	 * An item representing a value on stack.
	 */
	class StackItem extends Item {

		StackItem(int typecode) {
			super(typecode);
		}

		Item load() {
			return this;
		}

		void duplicate() {
			code.emitop(width() == 2 ? dup2 : dup);
		}

		void drop() {
			code.emitop(width() == 2 ? pop2 : pop);
		}

		void stash(int toscode) {
			code.emitop((width() == 2 ? dup_x2 : dup_x1) + 3
					* (Code.width(toscode) - 1));
		}

		int width() {
			return Code.width(typecode);
		}

		public String toString() {
			return "stack(" + typecodeNames[typecode] + ")";
		}
	}

	/**
	 * An item representing an indexed expression.
	 */
	class IndexedItem extends Item {

		IndexedItem(Type type) {
			super(Code.typecode(type));
		}

		Item load() {
			code.emitop(iaload + typecode);
			return stackItem[typecode];
		}

		Item store() {
			code.emitop(iastore + typecode);
			return voidItem;
		}

		void duplicate() {
			code.emitop(dup2);
		}

		void drop() {
			code.emitop(pop2);
		}

		void stash(int toscode) {
			code.emitop(dup_x2 + 3 * (Code.width(toscode) - 1));
		}

		int width() {
			return 2;
		}

		public String toString() {
			return "indexed(" + ByteCodes.typecodeNames[typecode] + ")";
		}
	}

	/**
	 * An item representing `this' or `super'.
	 */
	class SelfItem extends Item {

		/**
		 * Flag which determines whether this item represents `this' or `super'.
		 */
		boolean isSuper;

		SelfItem(boolean isSuper) {
			super(OBJECTcode);
			this.isSuper = isSuper;
		}

		Item load() {
			code.emitop(aload_0);
			return stackItem[typecode];
		}

		public String toString() {
			return isSuper ? "super" : "this";
		}
	}

	/**
	 * An item representing a local variable.
	 */
	class LocalItem extends Item {

		/**
		 * The variable's register.
		 */
		int reg;

		/**
		 * The variable's type.
		 */
		Type type;

		LocalItem(Type type, int reg) {
			super(Code.typecode(type));
			assert reg >= 0;
			this.type = type;
			this.reg = reg;
		}

		Item load() {
			if (reg <= 3)
				code.emitop(iload_0 + Code.truncate(typecode) * 4 + reg);
			else
				code.emitop1w(iload + Code.truncate(typecode), reg);
			return stackItem[typecode];
		}

		Item store() {
			if (reg <= 3)
				code.emitop(istore_0 + Code.truncate(typecode) * 4 + reg);
			else
				code.emitop1w(istore + Code.truncate(typecode), reg);
			code.setDefined(reg);
			return voidItem;
		}

		void incr(int x) {
			if (typecode == INTcode) {
				code.emitop1w(iinc, reg);
				if (reg > 255)
					code.emit2(x);
				else
					code.emit1(x);
			} else {
				load();
				if (x >= 0) {
					makeImmediateItem(syms.intType, new Integer(x)).load();
					code.emitop(iadd);
				} else {
					makeImmediateItem(syms.intType, new Integer(-x)).load();
					code.emitop(isub);
				}
				makeStackItem(syms.intType).coerce(typecode);
				store();
			}
		}

		public String toString() {
			return "localItem(type=" + type + "; reg=" + reg + ")";
		}
	}

	/**
	 * An item representing a static variable or method.
	 */
	class StaticItem extends Item {

		/**
		 * The represented symbol.
		 */
		Symbol member;

		StaticItem(Symbol member) {
			super(Code.typecode(member.erasure()));
			this.member = member;
		}

		Item load() {
			code.emitop(getstatic, Code.width(typecode));
			code.emit2(pool.put(member));
			return stackItem[typecode];
		}

		Item store() {
			code.emitop(putstatic, -Code.width(typecode));
			code.emit2(pool.put(member));
			return voidItem;
		}

		Item invoke() {
			MethodType mtype = (MethodType) member.erasure();
			int argsize = Code.width(mtype.argtypes);
			int rescode = Code.typecode(mtype.restype);
			int sdiff = Code.width(rescode) - argsize;
			code.emitop(invokestatic, sdiff);
			code.emit2(pool.put(member));
			return stackItem[rescode];
		}

		public String toString() {
			return "static(" + member + ")";
		}
	}

	/**
	 * An item representing an instance variable or method.
	 */
	class MemberItem extends Item {

		/**
		 * The represented symbol.
		 */
		Symbol member;

		/**
		 * Flag that determines whether or not access is virtual.
		 */
		boolean nonvirtual;

		MemberItem(Symbol member, boolean nonvirtual) {
			super(Code.typecode(member.erasure()));
			this.member = member;
			this.nonvirtual = nonvirtual;
		}

		Item load() {
			code.emitop(getfield, Code.width(typecode) - 1);
			code.emit2(pool.put(member));
			return stackItem[typecode];
		}

		Item store() {
			code.emitop(putfield, -Code.width(typecode) - 1);
			code.emit2(pool.put(member));
			return voidItem;
		}

		Item invoke() {
			MethodType mtype = (MethodType) member.externalType();
			int argsize = Code.width(mtype.argtypes);
			int rescode = Code.typecode(mtype.restype);
			int sdiff = Code.width(rescode) - argsize;
			if ((member.owner.flags() & Flags.INTERFACE) != 0) {
				code.emitop(invokeinterface, sdiff - 1);
				code.emit2(pool.put(member));
				code.emit1(argsize + 1);
				code.emit1(0);
			} else if (nonvirtual) {
				code.emitop(invokespecial, sdiff - 1);
				code.emit2(pool.put(member));
			} else {
				code.emitop(invokevirtual, sdiff - 1);
				code.emit2(pool.put(member));
			}
			return stackItem[rescode];
		}

		void duplicate() {
			stackItem[OBJECTcode].duplicate();
		}

		void drop() {
			stackItem[OBJECTcode].drop();
		}

		void stash(int toscode) {
			stackItem[OBJECTcode].stash(toscode);
		}

		int width() {
			return 1;
		}

		public String toString() {
			return "member(" + member + (nonvirtual ? " nonvirtual)" : ")");
		}
	}

	/**
	 * An item representing a literal.
	 */
	class ImmediateItem extends Item {

		/**
		 * The literal's value.
		 */
		Object value;

		ImmediateItem(Type type, Object value) {
			super(Code.typecode(type));
			this.value = value;
		}

		private void ldc() {
			int idx = pool.put(value);
			if (typecode == LONGcode || typecode == DOUBLEcode) {
				code.emitop(ldc2w, 2);
				code.emit2(idx);
			} else if (idx <= 255) {
				code.emitop(ldc1, 1);
				code.emit1(idx);
			} else {
				code.emitop(ldc2, 1);
				code.emit2(idx);
			}
		}

		Item load() {
			switch (typecode) {
			case INTcode:

			case BYTEcode:

			case SHORTcode:

			case CHARcode:
				int ival = ((Number) value).intValue();
				if (-1 <= ival && ival <= 5)
					code.emitop(iconst_0 + ival);
				else if (Byte.MIN_VALUE <= ival && ival <= Byte.MAX_VALUE)
					code.emitop1(bipush, ival);
				else if (Short.MIN_VALUE <= ival && ival <= Short.MAX_VALUE)
					code.emitop2(sipush, ival);
				else
					ldc();
				break;

			case LONGcode:
				long lval = ((Number) value).longValue();
				if (lval == 0 || lval == 1)
					code.emitop(lconst_0 + (int) lval);
				else
					ldc();
				break;

			case FLOATcode:
				float fval = ((Number) value).floatValue();
				if (isPosZero(fval) || fval == 1.0 || fval == 2.0)
					code.emitop(fconst_0 + (int) fval);
				else {
					ldc();
				}
				break;

			case DOUBLEcode:
				double dval = ((Number) value).doubleValue();
				if (isPosZero(dval) || dval == 1.0)
					code.emitop(dconst_0 + (int) dval);
				else
					ldc();
				break;

			case OBJECTcode:
				ldc();
				break;

			default:
				assert false;

			}
			return stackItem[typecode];
		}

		/**
		 * Return true iff float number is positive 0.
		 */
		private boolean isPosZero(float x) {
			return x == 0.0F && 1.0F / x > 0.0F;
		}

		/**
		 * Return true iff double number is positive 0.
		 */
		private boolean isPosZero(double x) {
			return x == 0.0 && 1.0 / x > 0.0;
		}

		CondItem mkCond() {
			int ival = ((Number) value).intValue();
			return makeCondItem(ival != 0 ? goto_ : dontgoto);
		}

		Item coerce(int targetcode) {
			if (typecode == targetcode) {
				return this;
			} else {
				switch (targetcode) {
				case INTcode:
					if (Code.truncate(typecode) == INTcode)
						return this;
					else
						return new ImmediateItem(syms.intType, new Integer(
								((Number) value).intValue()));

				case LONGcode:
					return new ImmediateItem(syms.longType, new Long(
							((Number) value).longValue()));

				case FLOATcode:
					return new ImmediateItem(syms.floatType, new Float(
							((Number) value).floatValue()));

				case DOUBLEcode:
					return new ImmediateItem(syms.doubleType, new Double(
							((Number) value).doubleValue()));

				case BYTEcode:
					return new ImmediateItem(syms.byteType, new Integer(
							(byte) ((Number) value).intValue()));

				case CHARcode:
					return new ImmediateItem(syms.charType, new Integer(
							(char) ((Number) value).intValue()));

				case SHORTcode:
					return new ImmediateItem(syms.shortType, new Integer(
							(short) ((Number) value).intValue()));

				default:
					return super.coerce(targetcode);

				}
			}
		}

		public String toString() {
			return "immediate(" + value + ")";
		}
	}

	/**
	 * An item representing an assignment expressions.
	 */
	class AssignItem extends Item {

		/**
		 * The item representing the assignment's left hand side.
		 */
		Item lhs;

		AssignItem(Item lhs) {
			super(lhs.typecode);
			this.lhs = lhs;
		}

		Item load() {
			lhs.stash(typecode);
			lhs.store();
			return stackItem[typecode];
		}

		void duplicate() {
			load().duplicate();
		}

		void drop() {
			lhs.store();
		}

		void stash(int toscode) {
			assert false;
		}

		int width() {
			return lhs.width() + Code.width(typecode);
		}

		public String toString() {
			return "assign(lhs = " + lhs + ")";
		}
	}

	/**
	 * An item representing a conditional or unconditional jump.
	 */
	class CondItem extends Item {

		/**
		 * A chain encomassing all jumps that can be taken if the condition
		 * evaluates to true.
		 */
		Chain trueJumps;

		/**
		 * A chain encomassing all jumps that can be taken if the condition
		 * evaluates to false.
		 */
		Chain falseJumps;

		/**
		 * The jump's opcode.
		 */
		int opcode;
		Tree tree;

		CondItem(int opcode, Chain truejumps, Chain falsejumps) {
			super(BYTEcode);
			this.opcode = opcode;
			this.trueJumps = truejumps;
			this.falseJumps = falsejumps;
		}

		Item load() {
			Chain trueChain = null;
			Chain falseChain = jumpFalse();
			if (trueJumps != null || opcode != dontgoto) {
				code.resolve(trueJumps);
				code.emitop(iconst_1);
				trueChain = code.branch(goto_);
			}
			if (falseChain != null) {
				code.resolve(falseChain);
				code.emitop(iconst_0);
			}
			code.resolve(trueChain);
			return stackItem[typecode];
		}

		void duplicate() {
			load().duplicate();
		}

		void drop() {
			load().drop();
		}

		void stash(int toscode) {
			assert false;
		}

		CondItem mkCond() {
			return this;
		}

		Chain jumpTrue() {
			if (tree == null)
				return code.mergeChains(trueJumps, code.branch(opcode));
			int startpc = code.curPc();
			Chain c = code.mergeChains(trueJumps, code.branch(opcode));
			code.crt.put(tree, CRTable.CRT_BRANCH_TRUE, startpc, code.curPc());
			return c;
		}

		Chain jumpFalse() {
			if (tree == null)
				return code.mergeChains(falseJumps, code.branch(code
						.negate(opcode)));
			int startpc = code.curPc();
			Chain c = code.mergeChains(falseJumps, code.branch(code
					.negate(opcode)));
			code.crt.put(tree, CRTable.CRT_BRANCH_FALSE, startpc, code.curPc());
			return c;
		}

		CondItem negate() {
			CondItem c = new CondItem(code.negate(opcode), falseJumps,
					trueJumps);
			c.tree = tree;
			return c;
		}

		int width() {
			return -code.stackdiff[opcode];
		}

		boolean isTrue() {
			return falseJumps == null && opcode == goto_;
		}

		boolean isFalse() {
			return trueJumps == null && opcode == dontgoto;
		}

		public String toString() {
			return "cond(" + Code.mnem(opcode) + ")";
		}
	}
}
